#include "StdAfx.h"
#include "MTSafeAllocator.h"
#include "smartptr.h"
#include "zlib/zlib.h"
#include "ZipFileFormat.h"
#include "ZipDirStructures.h"
#include <time.h>
#include <stdlib.h>
#include <ISystem.h>
#include <IJobManager.h>
#include <CryThread.h>
#include "CryPak.h"
#include <IJobManager_JobDelegator.h>

using namespace ZipFile;

void ZlibInflateElement( void *pCompressed, void* pUncompressed, unsigned long compressedSize, unsigned long nUnCompressedSize, unsigned long* pUncompressedSize, int *pReturnCode, CMTSafeHeap*pHeap );

DECLARE_JOB("zlib_inflate", TInflateJob, ZlibInflateElement )

// function to do the zlib decompression, on SPU, we use ZlibInflateElement, which is defined in zlib_spu (which in case only forward to edge zlib decompression)
void ZlibInflateElement( void *pCompressed, void* pUncompressed, unsigned long compressedSize, unsigned long nUnCompressedSize, unsigned long* pUncompressedSize, int *pReturnCode, CMTSafeHeap*pHeap ) 
{
	FUNCTION_PROFILER( gEnv->pSystem, PROFILE_SYSTEM );
	int err;
	z_stream stream;

	stream.next_in = (Bytef*)pCompressed;
	stream.avail_in = (uInt)compressedSize;

	stream.next_out = (Bytef*)pUncompressed;
	stream.avail_out = (uInt)nUnCompressedSize;

	stream.zalloc = CMTSafeHeap::StaticAlloc;
	stream.zfree = CMTSafeHeap::StaticFree;
	stream.opaque = pHeap;

	err = inflateInit2(&stream, -MAX_WBITS);
	if (err != Z_OK)
	{
		*pReturnCode = err;
		return;
	}
	// for some strange reason, passing Z_FINISH doesn't work - 
	// it seems the stream isn't finished for some files and 
	// inflate returns an error due to stream-end-not-reached (though expected) problem
	err = inflate(&stream, Z_SYNC_FLUSH);
	if (err != Z_STREAM_END && err != Z_OK)
	{
		inflateEnd(&stream);
		*pReturnCode = err == Z_OK ? Z_BUF_ERROR : err;
		return;
	}

	*pUncompressedSize = stream.total_out;

	err = inflateEnd(&stream);
	*pReturnCode = err;
}

//////////////////////////////////////////////////////////////////////////
void ZipDir::CZipFile::LoadToMemory(IMemoryBlock* pData)
{
	LOADING_TIME_PROFILE_SECTION;

	if (!m_pInMemoryData)
	{
		const char *szUsage = "In Memory Zip File";
		m_nCursor = 0;

		if (pData)
		{
			m_pInMemoryData = (ICustomMemoryBlock*) pData;
			m_pInMemoryData->AddRef();

			m_nSize = pData->GetSize();
		} else {
			FILE * realfile = m_file;

			CIOWrapper::Fseek(realfile, 0, SEEK_END);
			size_t nFileSize = (size_t)CIOWrapper::FTell(realfile);

			m_pInMemoryData = ((CCryPak*)(gEnv->pCryPak))->GetInMemoryPakHeap()->AllocateBlock( nFileSize,szUsage );
			m_pInMemoryData->AddRef();

			m_nSize = nFileSize;

			CIOWrapper::Fseek(realfile, 0, SEEK_SET);
			CIOWrapper::Fread(m_pInMemoryData->GetData(), nFileSize, 1, realfile);
		}
	}
}

//////////////////////////////////////////////////////////////////////////
void ZipDir::CZipFile::UnloadFromMemory()
{
	SAFE_RELEASE(m_pInMemoryData);
}

//////////////////////////////////////////////////////////////////////////
void ZipDir::CZipFile::Close( bool bUnloadFromMem )
{
	if (m_file)
		CIOWrapper::Fclose(m_file);

	if (bUnloadFromMem)
	{
		UnloadFromMemory();
	}

	m_nCursor = 0;
	m_nSize = 0;
}

#ifndef OPTIMIZED_READONLY_ZIP_ENTRY
//////////////////////////////////////////////////////////////////////////
ZipDir::FileEntry::FileEntry(const CDRFileHeader& header,const SExtraZipFileData &extra)
{
	this->desc              = header.desc;
	this->nFileHeaderOffset = header.lLocalHeaderOffset;
	//this->nFileDataOffset   = INVALID_DATA_OFFSET; // we don't know yet
	this->nMethod           = header.nMethod;
	this->nNameOffset       = 0; // we don't know yet
	this->nLastModTime      = header.nLastModTime;
	this->nLastModDate      = header.nLastModDate;
	this->nNTFS_LastModifyTime = extra.nLastModifyTime;

	// make an estimation (at least this offset should be there), but we don't actually know yet
	this->nFileDataOffset   = header.lLocalHeaderOffset + sizeof (ZipFile::LocalFileHeader) + header.nFileNameLength + header.nExtraFieldLength;
	this->nEOFOffset        = header.lLocalHeaderOffset + sizeof (ZipFile::LocalFileHeader) + header.nFileNameLength + header.nExtraFieldLength + header.desc.lSizeCompressed;
}
#endif //#ifndef OPTIMIZED_READONLY_ZIP_ENTRY


// Uncompresses raw (without wrapping) data that is compressed with method 8 (deflated) in the Zip file
// returns one of the Z_* errors (Z_OK upon success)
// This function just mimics the standard uncompress (with modification taken from unzReadCurrentFile)
// with 2 differences: there are no 16-bit checks, and 
// it initializes the inflation to start without waiting for compression method byte, as this is the 
// way it's stored into zip file
int ZipDir::ZipRawUncompress (CMTSafeHeap*pHeap, void* pUncompressed, unsigned long* pDestSize, const void* pCompressed, unsigned long nSrcSize)
{
  LOADING_TIME_PROFILE_SECTION(gEnv->pSystem);





	int nReturnCode;

	
	// forward to the job system, which will either offload the job to a secondary processor (the SPUs), or directly invoke the function in the calling context
	TInflateJob job( pCompressed, pUncompressed, nSrcSize, *pDestSize, pDestSize, &nReturnCode, pHeap);
	job.SetCacheMode(JobManager::SPUBackend::eCM_None);
	job.SetJobType(JobManager::SPUBackend::eOffLoadJob);
	job.Run();

	return nReturnCode;

}

// compresses the raw data into raw data. The buffer for compressed data itself with the heap passed. Uses method 8 (deflate)
// returns one of the Z_* errors (Z_OK upon success)
int ZipDir::ZipRawCompress (CMTSafeHeap*pHeap, const void* pUncompressed, unsigned long* pDestSize, void* pCompressed, unsigned long nSrcSize, int nLevel)
{
	z_stream stream;
	int err;

	stream.next_in   = (Bytef*)pUncompressed;
	stream.avail_in  = (uInt)nSrcSize;

	stream.next_out  = (Bytef*)pCompressed;
	stream.avail_out = (uInt)*pDestSize;

	stream.zalloc = CMTSafeHeap::StaticAlloc;
	stream.zfree  = CMTSafeHeap::StaticFree;
	stream.opaque = pHeap;

	err = deflateInit2 (&stream, nLevel, Z_DEFLATED, -MAX_WBITS, 9, Z_DEFAULT_STRATEGY);
	if (err != Z_OK) return err;

	err = deflate (&stream, Z_FINISH);
	if (err != Z_STREAM_END) {
		deflateEnd(&stream);
		return err == Z_OK ? Z_BUF_ERROR : err;
	}
	*pDestSize = stream.total_out;

	err = deflateEnd(&stream);
	return err;
}

// finds the subdirectory entry by the name, using the names from the name pool
// assumes: all directories are sorted in alphabetical order.
// case-sensitive (must be lower-case if case-insensitive search in Win32 is performed)
ZipDir::DirEntry* ZipDir::DirHeader::FindSubdirEntry(const char* szName)
{
	if (this->numDirs)
	{
		const char* pNamePool = GetNamePool();
		DirEntrySortPred pred(pNamePool);
		DirEntry* pBegin = GetSubdirEntry(0);
		DirEntry* pEnd = pBegin + this->numDirs;
		DirEntry* pEntry = std::lower_bound(pBegin,pEnd, szName, pred);
#if defined(LINUX)
		if (pEntry != pEnd && !strcasecmp(szName, pEntry->GetName(pNamePool)))
#else
		if (pEntry != pEnd && !strcmp(szName, pEntry->GetName(pNamePool)))
#endif
			return pEntry;
	}
	return NULL;
}

// finds the file entry by the name, using the names from the name pool
// assumes: all directories are sorted in alphabetical order.
// case-sensitive (must be lower-case if case-insensitive search in Win32 is performed)
ZipDir::FileEntry* ZipDir::DirHeader::FindFileEntry(const char* szName)
{
	if (this->numFiles)
	{
		const char* pNamePool = GetNamePool();
		DirEntrySortPred pred(pNamePool);
		FileEntry* pBegin = GetFileEntry(0);
		FileEntry* pEnd = pBegin + this->numFiles;
		FileEntry* pEntry = std::lower_bound(pBegin,pEnd, szName, pred);
#if defined(LINUX)
		if (pEntry != pEnd && !strcasecmp(szName, pEntry->GetName(pNamePool)))
#else
		if (pEntry != pEnd && !strcmp(szName, pEntry->GetName(pNamePool)))
#endif
			return pEntry;
	}
	return NULL;
}


// tries to refresh the file entry from the given file (reads fromthere if needed)
// returns the error code if the operation was impossible to complete
ZipDir::ErrorEnum ZipDir::Refresh (CZipFile*f, FileEntry* pFileEntry)
{
#ifndef OPTIMIZED_READONLY_ZIP_ENTRY

	if (pFileEntry->nFileDataOffset != pFileEntry->INVALID_DATA_OFFSET)
		return ZD_ERROR_SUCCESS;
	
	if (ZipDir::FSeek(f, pFileEntry->nFileHeaderOffset, SEEK_SET,pFileEntry))
//#ifdef WIN32
//  if (_fseeki64(f, (__int64)pFileEntry->nFileHeaderOffset, SEEK_SET))
//#elif LINUX
//	if (fseeko(f, (off_t)(pFileEntry->nFileHeaderOffset), SEEK_SET))
//#else
//  if (fseek(f, pFileEntry->nFileHeaderOffset, SEEK_SET))
//#endif
		return ZD_ERROR_IO_FAILED;

	// No validation in release or profile!

	// read the local file header and the name (for validation) into the buffer
	LocalFileHeader fileHeader;
	//if (1 != fread (&fileHeader, sizeof(fileHeader), 1, f))
	if (1 != ZipDir::FRead(f,&fileHeader, sizeof(fileHeader), 1, pFileEntry))
		return ZD_ERROR_IO_FAILED;
	SwapEndian(fileHeader);

	if (fileHeader.desc != pFileEntry->desc 
		|| fileHeader.nMethod                != pFileEntry->nMethod)
	{				
		char szErrDesc[1024];
		sprintf(szErrDesc,"ERROR: File header doesn't match previously cached file entry record (%s) \n fileheader desc=(%d,%d,%d), method=%d, \n fileentry desc=(%d,%d,%d),method=%d",
			"Unknown"/*f->_tmpfname*/,
		fileHeader.desc.lCRC32,fileHeader.desc.lSizeCompressed,fileHeader.desc.lSizeUncompressed,
		fileHeader.nMethod,
		pFileEntry->desc.lCRC32,pFileEntry->desc.lSizeCompressed,pFileEntry->desc.lSizeUncompressed,
		pFileEntry->nMethod);
		CryFatalError(szErrDesc);	

		return ZD_ERROR_IO_FAILED;
		//CryFatalError(szErrDesc);				
		//THROW_ZIPDIR_ERROR(ZD_ERROR_VALIDATION_FAILED,szErrDesc);
	}
	pFileEntry->nFileDataOffset = pFileEntry->nFileHeaderOffset + sizeof(LocalFileHeader) + fileHeader.nFileNameLength + fileHeader.nExtraFieldLength;
	pFileEntry->nEOFOffset      = pFileEntry->nFileDataOffset + pFileEntry->desc.lSizeCompressed;

#endif //#ifndef OPTIMIZED_READONLY_ZIP_ENTRY

	return ZD_ERROR_SUCCESS;
}


// writes into the file local header (NOT including the name, only the header structure)
// the file must be opened both for reading and writing
ZipDir::ErrorEnum ZipDir::UpdateLocalHeader (FILE*f, FileEntry* pFileEntry)
{
#ifndef OPTIMIZED_READONLY_ZIP_ENTRY

#ifdef WIN32
	if (_fseeki64 (f, (__int64)pFileEntry->nFileHeaderOffset, SEEK_SET))
#elif LINUX
	if (fseeko(f, (off_t)(pFileEntry->nFileHeaderOffset), SEEK_SET))
#else
  if (fseek (f, pFileEntry->nFileHeaderOffset, SEEK_SET))
#endif
		return ZD_ERROR_IO_FAILED;

	LocalFileHeader h;
	if (1 != fread(&h, sizeof(h),1, f))
		return ZD_ERROR_IO_FAILED;
	SwapEndian(h);

	assert (h.lSignature == h.SIGNATURE);

	h.desc.lCRC32 = pFileEntry->desc.lCRC32;
	h.desc.lSizeCompressed = pFileEntry->desc.lSizeCompressed;
	h.desc.lSizeUncompressed = pFileEntry->desc.lSizeUncompressed;
	h.nMethod = pFileEntry->nMethod;
	h.nFlags &= ~GPF_ENCRYPTED; // we don't support encrypted files

#ifdef WIN32
	if (_fseeki64 (f, (__int64)pFileEntry->nFileHeaderOffset, SEEK_SET))
#elif LINUX
	if (fseeko(f, (off_t)(pFileEntry->nFileHeaderOffset), SEEK_SET))
#else
  if (fseek (f, pFileEntry->nFileHeaderOffset, SEEK_SET))
#endif
		return ZD_ERROR_IO_FAILED;

	if (1 != fwrite (&h, sizeof(h), 1, f))
		return ZD_ERROR_IO_FAILED;

#endif //#ifndef OPTIMIZED_READONLY_ZIP_ENTRY
	return ZD_ERROR_SUCCESS;
}


// writes into the file local header - without Extra data
// puts the new offset to the file data to the file entry
// in case of error can put INVALID_DATA_OFFSET into the data offset field of file entry
ZipDir::ErrorEnum ZipDir::WriteLocalHeader (FILE*f, FileEntry* pFileEntry, const char* szRelativePath, bool encrypt)
{
#ifndef OPTIMIZED_READONLY_ZIP_ENTRY
	size_t nFileNameLength = strlen(szRelativePath);
	size_t nHeaderSize = sizeof(LocalFileHeader) + nFileNameLength;

	pFileEntry->nFileDataOffset = pFileEntry->nFileHeaderOffset + nHeaderSize;
	pFileEntry->nEOFOffset = pFileEntry->nFileDataOffset + pFileEntry->desc.lSizeCompressed;

#ifdef WIN32
	if (_fseeki64 (f, (__int64)pFileEntry->nFileHeaderOffset, SEEK_SET))
#elif LINUX
	if (fseeko(f, (off_t)(pFileEntry->nFileHeaderOffset), SEEK_SET))
#else
  if (fseek (f, pFileEntry->nFileHeaderOffset, SEEK_SET))
#endif
		return ZD_ERROR_IO_FAILED;

	if (encrypt)
	{
		std::vector<unsigned char> garbage;
		garbage.resize(nHeaderSize);
		for (size_t i = 0; i < nHeaderSize; ++i)
		{
			garbage[i] = rand() * 0xff / RAND_MAX;
		}

		if (fwrite(&garbage[0], nHeaderSize, 1, f) != 1)
		{
			return ZD_ERROR_IO_FAILED;
		}
	}
	else
	{
		LocalFileHeader h;
		h.lSignature        = h.SIGNATURE;
		h.nVersionNeeded    = 10;
		h.nFlags            = 0;
		h.nMethod           = pFileEntry->nMethod;
		h.nLastModDate      = pFileEntry->nLastModDate;
		h.nLastModTime      = pFileEntry->nLastModTime;
		h.desc              = pFileEntry->desc;
		h.nFileNameLength   = (unsigned short)strlen(szRelativePath);
		h.nExtraFieldLength = 0;

		pFileEntry->nFileDataOffset = pFileEntry->nFileHeaderOffset + sizeof(h) + h.nFileNameLength;
		pFileEntry->nEOFOffset = pFileEntry->nFileDataOffset + pFileEntry->desc.lSizeCompressed;

		if (1 != fwrite (&h, sizeof(h),1, f))
			return ZD_ERROR_IO_FAILED;

		if (nFileNameLength > 0)
		{
			if (1 != fwrite(szRelativePath, nFileNameLength, 1, f))
				return ZD_ERROR_IO_FAILED;
		}
	}

#endif //#ifndef OPTIMIZED_READONLY_ZIP_ENTRY

	return ZD_ERROR_SUCCESS;
}


// conversion routines for the date/time fields used in Zip
uint16 ZipDir::DOSDate(tm* t)
{
	return
		 ((t->tm_year - 80) << 9)
		|(t->tm_mon << 5)
		|t->tm_mday;
}

uint16 ZipDir::DOSTime(tm* t)
{
	return
		 ((t->tm_hour) << 11)
		|((t->tm_min) << 5)
		|((t->tm_sec) >> 1);
}

#ifndef OPTIMIZED_READONLY_ZIP_ENTRY
// sets the current time to modification time
// calculates CRC32 for the new data
void ZipDir::FileEntry::OnNewFileData(void* pUncompressed, unsigned nSize, unsigned nCompressedSize, unsigned nCompressionMethod, bool bContinuous )
{
	time_t nTime;
	time(&nTime);
	tm* t = localtime(&nTime);

	// While local time converts the month to a 0 to 11 interval...
	// ...the pack file expects months from 1 to 12...
	// Therefore, for correct date, we have to do t->tm_mon+=1;
	t->tm_mon+=1;

	this->nLastModTime = DOSTime(t);
	this->nLastModDate = DOSDate(t);

#ifdef WIN32
	FILETIME ft;
	::GetSystemTimeAsFileTime(&ft);
	LARGE_INTEGER lt;
	lt.HighPart = ft.dwHighDateTime;
	lt.LowPart = ft.dwLowDateTime;
	this->nNTFS_LastModifyTime = lt.QuadPart;
#endif

	if( !bContinuous )
	{
		this->desc.lCRC32 = crc32(0L, Z_NULL, 0);
		this->desc.lSizeCompressed = nCompressedSize;
		this->desc.lSizeUncompressed = nSize;
	}

	// we'll need CRC32 of the file to pack it
	this->desc.lCRC32 = crc32(this->desc.lCRC32, (Bytef*)pUncompressed, nSize);

	this->nMethod = nCompressionMethod;
}

uint64 ZipDir::FileEntry::GetModificationTime()
{
	if (nNTFS_LastModifyTime != 0)
		return nNTFS_LastModifyTime;

	// TODO/TIME: check and test
	SYSTEMTIME st;
	st.wYear  = (nLastModDate>>9)+1980;
	st.wMonth = ((nLastModDate>>5)&0xF);
	st.wDay   =	(nLastModDate&0x1F);
	st.wHour  = (nLastModTime>>11);
	st.wMinute= (nLastModTime>>5)&0x3F;
	st.wSecond= (nLastModTime<<1)&0x3F;
	st.wMilliseconds = 0;
	FILETIME ft;
	SystemTimeToFileTime(&st, &ft);
	LARGE_INTEGER lt;
	lt.HighPart = ft.dwHighDateTime;
	lt.LowPart = ft.dwLowDateTime;
	return lt.QuadPart;
}
#endif //#ifndef OPTIMIZED_READONLY_ZIP_ENTRY


const char* ZipDir::DOSTimeCStr(uint16 nTime)
{
	static char szBuf[16];
	sprintf (szBuf, "%02d:%02d.%02d", (nTime >> 11), ((nTime&((1<<11)-1))>>5), ((nTime&((1<<5)-1))<<1));
	return szBuf;
}

const char* ZipDir::DOSDateCStr(uint16 nTime)
{
	static char szBuf[32];
	sprintf (szBuf, "%02d.%02d.%04d", (nTime&0x1F), (nTime>>5)&0xF, (nTime >> 9) + 1980);
	return szBuf;
}

const char* ZipDir::Error::getError()
{
	switch (this->nError)
	{
#define DECLARE_ERROR(x) case ZD_ERROR_##x: return #x;
	DECLARE_ERROR(SUCCESS);
	DECLARE_ERROR(IO_FAILED);
	DECLARE_ERROR(UNEXPECTED);
	DECLARE_ERROR(UNSUPPORTED);
	DECLARE_ERROR(INVALID_SIGNATURE);
	DECLARE_ERROR(ZIP_FILE_IS_CORRUPT);
	DECLARE_ERROR(DATA_IS_CORRUPT);
	DECLARE_ERROR(NO_CDR);
	DECLARE_ERROR(CDR_IS_CORRUPT);
	DECLARE_ERROR(NO_MEMORY);
	DECLARE_ERROR(VALIDATION_FAILED);
	DECLARE_ERROR(CRC32_CHECK);
	DECLARE_ERROR(ZLIB_FAILED);
	DECLARE_ERROR(ZLIB_CORRUPTED_DATA);
	DECLARE_ERROR(ZLIB_NO_MEMORY);
	DECLARE_ERROR(CORRUPTED_DATA);
	DECLARE_ERROR(INVALID_CALL);
	DECLARE_ERROR(NOT_IMPLEMENTED);
	DECLARE_ERROR(FILE_NOT_FOUND);
	DECLARE_ERROR(DIR_NOT_FOUND);
	DECLARE_ERROR(NAME_TOO_LONG);
	DECLARE_ERROR(INVALID_PATH);
	DECLARE_ERROR(FILE_ALREADY_EXISTS);
#undef DECLARE_ERROR
	default: return "Unknown ZD_ERROR code";
	}
}

//////////////////////////////////////////////////////////////////////////
uint32 ZipDir::FileNameHash( const char *filename )
{
	stack_string str = filename;
	str.replace( '/','\\' );

	uLong uCRC32 = crc32(0L, Z_NULL, 0);
	uCRC32 = crc32(uCRC32, (Bytef*)str.c_str(),str.length() );
	return uCRC32;
}

//////////////////////////////////////////////////////////////////////////
inline void btea(uint32 *v, int n, uint32 const k[4])
{
#define TEA_DELTA 0x9e3779b9
#define TEA_MX (((z>>5^y<<2) + (y>>3^z<<4)) ^ ((sum^y) + (k[(p&3)^e] ^ z)))
	uint32 y, z, sum;
	unsigned p, rounds, e;
	if (n > 1) {          /* Coding Part */
		rounds = 6 + 52/n;
		sum = 0;
		z = v[n-1];
		do {
			sum += TEA_DELTA;
			e = (sum >> 2) & 3;
			for (p=0; p<uint32(n-1); p++) {
				y = v[p+1]; 
				z = v[p] += TEA_MX;
			}
			y = v[0];
			z = v[n-1] += TEA_MX;
		} while (--rounds);
	} else if (n < -1) {  /* Decoding Part */
		n = -n;
		rounds = 6 + 52/n;
		sum = rounds*TEA_DELTA;
		y = v[0];
		do {
			e = (sum >> 2) & 3;
			for (p=uint32(n-1); p>0; p--) {
				z = v[p-1];
				y = v[p] -= TEA_MX;
			}
			z = v[n-1];
			y = v[0] -= TEA_MX;
		} while ((sum -= TEA_DELTA) != 0);
	}
#undef TEA_DELTA
#undef TEA_MX
}

//////////////////////////////////////////////////////////////////////////
inline void SwapByteOrder(uint32* values, size_t count)
{
	for (uint32 *w = values, *e = values + count; w != e; ++w)
	{
		*w = (*w >> 24) + ((*w >> 8) & 0xff00) + ((*w & 0xff00) << 8) + (*w << 24);
	}
}

//////////////////////////////////////////////////////////////////////////
#ifndef OPTIMIZED_READONLY_ZIP_ENTRY
// anyway, const data will be assigned to text section, regardless of its location, global or local
extern const uint32 preciousData[];
const uint32 preciousData[4] = { 0xc968fb67, 0x8f9b4267, 0x85399e84, 0xf9b99dc4 };
void ZipDir::Encrypt(char* buffer, size_t size)
{
	uint32* intBuffer = (uint32*)buffer;
	const int encryptedLen = size >> 2;

#if !defined(XENON) && !defined(PS3)
	SwapByteOrder(intBuffer, encryptedLen);
#endif

	btea(intBuffer, encryptedLen, preciousData);

#if !defined(XENON) && !defined(PS3)
	SwapByteOrder(intBuffer, encryptedLen);
#endif
}
#endif

//////////////////////////////////////////////////////////////////////////

void ZipDir::Decrypt(char* buffer, size_t size)
{
	uint32* intBuffer = (uint32*)buffer;
	const int encryptedLen = size >> 2;

#if !defined(XENON) && !defined(PS3)
	SwapByteOrder(intBuffer, encryptedLen);
#endif

	btea(intBuffer, -encryptedLen, preciousData);

#if !defined(XENON) && !defined(PS3)
	SwapByteOrder(intBuffer, encryptedLen);
#endif
}

////////////////////////////////////////////////////////////////////////// STREAM CIPHER COPY
// stream cipher is in crynetwork, but we need to use it before crynetwork is initialized
// copy and paste code here for now, not worth refactoring stuff
class CStreamCipherCopy
{
public:
	void	Init(const uint8* key, int keyLen);
	void	Encrypt(const uint8* input, int inputLen, uint8* output)	{ ProcessBuffer(input, inputLen, output, true); }
	void	Decrypt(const uint8* input, int inputLen, uint8* output)	{ ProcessBuffer(input, inputLen, output, true); }
	void	EncryptStream(const uint8* input, int inputLen, uint8* output)	{ ProcessBuffer(input, inputLen, output, false); }
	void	DecryptStream(const uint8* input, int inputLen, uint8* output)	{ ProcessBuffer(input, inputLen, output, false); }

private:
	uint8		GetNext();
	void		ProcessBuffer(const uint8* input, int inputLen, uint8* output, bool resetKey);

	uint8		m_StartS[256];
	uint8		m_S[256];
	int			m_StartI;
	int			m_I;
	int			m_StartJ;
	int			m_J;
};

void CStreamCipherCopy::Init(const uint8* key, int keyLen)
{
	int i, j;

	for (i = 0; i < 256; i++)
	{
		m_S[i] = i;
	}

	if (key)
	{
		for (i = j = 0; i < 256; i++)
		{
			uint8 temp;

			j = (j + key[i%keyLen] + m_S[i])&255;
			temp = m_S[i];
			m_S[i] = m_S[j];
			m_S[j] = temp;
		}
	}

	m_I = m_J = 0;

	for (i = 0; i < 1024; i++)
	{
		GetNext();
	}

	memcpy(m_StartS, m_S, sizeof(m_StartS));

	m_StartI = m_I;
	m_StartJ = m_J;
}

uint8 CStreamCipherCopy::GetNext()
{
	uint8 tmp;

	m_I = (m_I + 1)&0xff;
	m_J = (m_J + m_S[m_I])&0xff;

	tmp = m_S[m_J];
	m_S[m_J] = m_S[m_I];
	m_S[m_I] = tmp;

	return m_S[(m_S[m_I] + m_S[m_J])&0xff];
}

void CStreamCipherCopy::ProcessBuffer(const uint8* input, int inputLen, uint8* output, bool resetKey)
{
	if(resetKey)
	{
		memcpy(m_S, m_StartS, sizeof(m_S));
		m_I = m_StartI;
		m_J = m_StartJ;
	}

	for (int i = 0; i < inputLen; i++)
	{
		output[i] = input[i]^GetNext();
	}
}

#if !defined(OPTIMIZED_READONLY_ZIP_ENTRY)
////////////////////////////////////////////////////////////////////////// STREAM CIPHER COPY

//////////////////////////////////////////////////////////////////////////
void ZipDir::StreamCipher(char* buffer, size_t size, uint32 inKey)
{
	CStreamCipherCopy		cipher;
	cipher.Init((const uint8*)&inKey,sizeof(inKey));
	cipher.EncryptStream((uint8*)buffer,size,(uint8*)buffer);
}

//////////////////////////////////////////////////////////////////////////
void ZipDir::StreamCipher(char* buffer, const FileEntry *inEntry)
{
	StreamCipher(buffer,inEntry->desc.lSizeCompressed,GetStreamCipherKey(inEntry));
}
#endif

// TypeInfo implementations for CrySystem
#include "TypeInfo_impl.h"
#include "ZipFileFormat_info.h"
